package uk.co.deanwild.materialshowcaseview;


import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.PixelMap;

public class ShowcaseTooltip {
    private Context mContext;
    private ComponentContainer rootView;
    private Component view;
    private TooltipView tooltip_view;

    private ShowcaseTooltip(Context context) {
        this.tooltip_view = new TooltipView(context);
        this.mContext = context;
    }

    public static ShowcaseTooltip build(Context context) {
        return new ShowcaseTooltip(context);
    }

    public void configureTarget(ComponentContainer rootView, Component view) {
        this.rootView = rootView;
        this.view = view;
    }

    public ShowcaseTooltip corner(int corner) {
        this.tooltip_view.setAngle(corner);
        return this;
    }

    public ShowcaseTooltip arrowIsCenter(boolean isCenter) {
        this.tooltip_view.setArrowCenter(isCenter);
        return this;
    }

    public ShowcaseTooltip text(String content) {
        this.tooltip_view.setText(content);
        return this;
    }

    public ShowcaseTooltip textColor(int color) {
        this.tooltip_view.setTextColor(new Color(color));
        return this;
    }

    public void show(int toolTipDistance, int Alignment, int mShapePadding) {
        tooltip_view.setTextSize(AttrHelper.fp2px(13, mContext));
        tooltip_view.setPadding(AttrHelper.vp2px(8, mContext),
                AttrHelper.vp2px(8, mContext),
                AttrHelper.vp2px(8, mContext),
                AttrHelper.vp2px(8, mContext)
        );
        tooltip_view.setMultipleLine(true);
        tooltip_view.setLayoutConfig(new StackLayout.LayoutConfig(
                StackLayout.LayoutConfig.MATCH_PARENT,
                StackLayout.LayoutConfig.MATCH_CONTENT
        ));

        StackLayout.LayoutConfig layoutConfig = (StackLayout.LayoutConfig) tooltip_view.getLayoutConfig();

        if (Alignment == LayoutAlignment.BOTTOM) {
            tooltip_view.setArrowLocation(BubbleElement.ArrowLocation.BOTTOM);
            layoutConfig.alignment = LayoutAlignment.BOTTOM | LayoutAlignment.RIGHT;
            layoutConfig.setMargins(toolTipDistance, toolTipDistance, toolTipDistance, view.getHeight() + toolTipDistance + mShapePadding * 2);
            layoutConfig.width = StackLayout.LayoutConfig.MATCH_CONTENT;
            layoutConfig.height = StackLayout.LayoutConfig.MATCH_CONTENT;
            tooltip_view.setArrowPosition(570);
        } else {
            tooltip_view.setArrowLocation(BubbleElement.ArrowLocation.TOP);
            layoutConfig.alignment = LayoutAlignment.TOP;
            layoutConfig.setMargins(toolTipDistance, view.getHeight() + toolTipDistance + mShapePadding, toolTipDistance, toolTipDistance);
            layoutConfig.width = StackLayout.LayoutConfig.MATCH_PARENT;
            layoutConfig.height = StackLayout.LayoutConfig.MATCH_CONTENT;
        }
        tooltip_view.draw();
        rootView.addComponent(tooltip_view);
    }

    private static class TooltipView extends Text {
        // 气泡样式
        private BubbleElement bubbleElement;

        // 箭头方向
        private BubbleElement.ArrowLocation arrowLocation = BubbleElement.ArrowLocation.TOP;

        // 箭头宽度
        private float arrowWidth = BubbleElement.Builder.DEFAULT_ARROW_WITH;

        // 箭头高度
        private float arrowHeight = BubbleElement.Builder.DEFAULT_ARROW_HEIGHT;

        // 箭头位置
        private float arrowPosition = BubbleElement.Builder.DEFAULT_ARROW_POSITION;

        // 箭头是否居中
        private boolean arrowCenter = false;

        // 气泡圆角
        private float angle = BubbleElement.Builder.DEFAULT_ANGLE;

        // 气泡背景色
        private Color bubbleColor = BubbleElement.Builder.DEFAULT_BUBBLE_COLOR;

        public void setBubbleElement(BubbleElement bubbleElement) {
            this.bubbleElement = bubbleElement;
        }

        public void setArrowLocation(BubbleElement.ArrowLocation arrowLocation) {
            this.arrowLocation = arrowLocation;
        }

        public void setArrowWidth(float arrowWidth) {
            this.arrowWidth = arrowWidth;
        }

        public void setArrowHeight(float arrowHeight) {
            this.arrowHeight = arrowHeight;
        }

        public void setArrowPosition(float arrowPosition) {
            this.arrowPosition = arrowPosition;
        }

        public void setArrowCenter(boolean arrowCenter) {
            this.arrowCenter = arrowCenter;
        }

        public void setAngle(float angle) {
            this.angle = angle;
        }

        public void setBubbleColor(Color bubbleColor) {
            this.bubbleColor = bubbleColor;
        }

        /**
         * 构造方法
         *
         * @param context 上下文
         */
        public TooltipView(Context context) {
            this(context, null);
        }

        /**
         * 构造方法
         *
         * @param context 上下文
         * @param attrSet 自定义属性
         */
        public TooltipView(Context context, AttrSet attrSet) {
            this(context, attrSet, null);
        }

        /**
         * 构造方法
         *
         * @param context   上下文
         * @param attrSet   自定义属性
         * @param styleName 样式名称
         */
        public TooltipView(Context context, AttrSet attrSet, String styleName) {
            super(context, attrSet, styleName);
            initView(attrSet);
        }

        private void initView(AttrSet attrSet) {
            if (attrSet != null) {
                if (attrSet.getAttr("arrowLocation").isPresent()) {
                    String arrowLocationName = attrSet.getAttr("arrowLocation").get().getStringValue();
                    arrowLocation = BubbleElement.ArrowLocation.mapIntToName(arrowLocationName);
                }
                if (attrSet.getAttr("arrowWidth").isPresent()) {
                    arrowWidth = attrSet.getAttr("arrowWidth").get().getDimensionValue();
                }
                if (attrSet.getAttr("arrowHeight").isPresent()) {
                    arrowHeight = attrSet.getAttr("arrowHeight").get().getDimensionValue();
                }
                if (attrSet.getAttr("arrowPosition").isPresent()) {
                    arrowPosition = attrSet.getAttr("arrowPosition").get().getDimensionValue();
                }
                if (attrSet.getAttr("arrowCenter").isPresent()) {
                    arrowCenter = attrSet.getAttr("arrowCenter").get().getBoolValue();
                }
                if (attrSet.getAttr("angle").isPresent()) {
                    angle = attrSet.getAttr("angle").get().getDimensionValue();
                }
                if (attrSet.getAttr("bubbleColor").isPresent()) {
                    bubbleColor = attrSet.getAttr("bubbleColor").get().getColorValue();
                }
            }
        }

        public void draw() {
            setPadding();

            addDrawTask((component, canvas) -> {
                RectFloat rectF = new RectFloat(0, 0, getWidth(), getHeight());
                bubbleElement = new BubbleElement.Builder()
                        .rect(rectF)
                        .arrowLocation(arrowLocation)
                        .angle(angle)
                        .arrowHeight(arrowHeight)
                        .arrowWidth(arrowWidth)
                        .bubbleColor(bubbleColor)
                        .arrowPosition(arrowPosition)
                        .arrowCenter(arrowCenter)
                        .build();
                setBackground(bubbleElement);
            });
        }


        private void setPadding() {
            int left = getPaddingLeft();
            int right = getPaddingRight();
            int top = getPaddingTop();
            int bottom = getPaddingBottom();
            switch (arrowLocation) {
                case LEFT:
                    left += arrowWidth;
                    break;
                case RIGHT:
                    right += arrowWidth;
                    break;
                case TOP:
                    top += arrowHeight;
                    break;
                case BOTTOM:
                    bottom += arrowHeight;
                    break;
                default:
                    break;
            }
            setPadding(left, top, right, bottom);
        }
    }

    private static final class BubbleElement extends ShapeElement {
        private static final int CONSTANTS_0 = 0;
        private static final int CONSTANTS_2 = 2;
        private static final int CONSTANTS_90 = 90;
        private static final int CONSTANTS_180 = 180;
        private static final int CONSTANTS_270 = 270;

        // 浮点矩形
        private final RectFloat mRect;

        // 路径
        private final Path mPath = new Path();

        // 画笔
        private final Paint mPaint = new Paint();

        // 箭头宽度
        private final float mArrowWidth;

        // 圆角宽度（四角）
        private final float mAngle;

        // 箭头高度
        private final float mArrowHeight;

        // 箭头是否在边的中心
        private final boolean isArrowCenter;

        // 箭头位置（上下左右）
        private final ArrowLocation mArrowLocation;

        // 箭头
        private float mArrowPosition;

        // 气泡背景颜色
        private final Color bubbleColor;

        // 气泡类型
        private final BubbleType bubbleType;

        // 画布
        private final Canvas mCanvas;

        // 气泡背景图片
        private final PixelMap bubblePixelMap;
        private PixelMapShader mPixelMapShader;

        private BubbleElement(Builder builder) {
            this.mRect = builder.mRect;
            this.mAngle = builder.mAngle;
            this.mArrowHeight = builder.mArrowHeight;
            this.mArrowWidth = builder.mArrowWidth;
            this.mArrowPosition = builder.mArrowPosition;
            this.bubbleColor = builder.bubbleColor;
            this.mArrowLocation = builder.mArrowLocation;
            this.isArrowCenter = builder.isArrowCenter;
            this.bubblePixelMap = builder.bubblePixelMap;
            this.bubbleType = builder.bubbleType;
            this.mCanvas = builder.mCanvas;
            init();
        }

        private void init() {
            // 根据方向设置路径
            setPath(mArrowLocation, mPath);

            if (bubbleType == BubbleType.COLOR) {
                // 设置形状为路径
                setShape(PATH);

                // 设置背景颜色
                setRgbColor(RgbColor.fromArgbInt(bubbleColor.getValue()));
            } else {
                setShaderType(LINEAR_GRADIENT_SHADER_TYPE);
                if (bubblePixelMap == null) {
                    return;
                }

                if (mPixelMapShader == null) {
                    mPixelMapShader = new PixelMapShader(new PixelMapHolder(bubblePixelMap),
                            Shader.TileMode.CLAMP_TILEMODE, Shader.TileMode.CLAMP_TILEMODE);
                }
                setShaderMatrix();
                mPaint.setAntiAlias(true);
                mPaint.setShader(mPixelMapShader, Paint.ShaderType.LINEAR_SHADER);
                mCanvas.drawPath(mPath, mPaint);
            }
        }

        private void setPath(ArrowLocation arrowLocation, Path path) {
            switch (arrowLocation) {
                case LEFT:
                    setLeftPath(mRect, path);
                    break;
                case RIGHT:
                    setRightPath(mRect, path);
                    break;
                case TOP:
                    setTopPath(mRect, path);
                    break;
                case BOTTOM:
                    setBottomPath(mRect, path);
                    break;
                default:
                    break;
            }
            setPath(mPath);
        }

        private void setLeftPath(RectFloat rect, Path path) {
            if (isArrowCenter) {
                mArrowPosition = (rect.bottom - rect.top) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
            }

            path.moveTo(mArrowWidth + rect.left + mAngle, rect.top);
            path.lineTo(rect.getWidth() - mAngle, rect.top);
            path.arcTo(new RectFloat(rect.right - mAngle, rect.top, rect.right,
                    mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);
            path.lineTo(rect.right, rect.bottom - mAngle);
            path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle,
                    rect.right, rect.bottom), CONSTANTS_0, CONSTANTS_90);
            path.lineTo(rect.left + mArrowWidth + mAngle, rect.bottom);
            path.arcTo(new RectFloat(rect.left + mArrowWidth, rect.bottom - mAngle,
                    mAngle + rect.left + mArrowWidth, rect.bottom), CONSTANTS_90, CONSTANTS_90);
            path.lineTo(rect.left + mArrowWidth, mArrowHeight + mArrowPosition);
            path.lineTo(rect.left, mArrowPosition + mArrowHeight / CONSTANTS_2);
            path.lineTo(rect.left + mArrowWidth, mArrowPosition);
            path.lineTo(rect.left + mArrowWidth, rect.top + mAngle);
            path.arcTo(new RectFloat(rect.left + mArrowWidth, rect.top, mAngle
                    + rect.left + mArrowWidth, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
            path.close();
        }

        private void setTopPath(RectFloat rect, Path path) {
            if (isArrowCenter) {
                mArrowPosition = (rect.right - rect.left) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
            }

            path.moveTo(rect.left + Math.min(mArrowPosition, mAngle), rect.top + mArrowHeight);
            path.lineTo(rect.left + mArrowPosition, rect.top + mArrowHeight);
            path.lineTo(rect.left + mArrowWidth / CONSTANTS_2 + mArrowPosition, rect.top);
            path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.top + mArrowHeight);
            path.lineTo(rect.right - mAngle, rect.top + mArrowHeight);

            path.arcTo(new RectFloat(rect.right - mAngle,
                    rect.top + mArrowHeight, rect.right, mAngle + rect.top + mArrowHeight), CONSTANTS_270, CONSTANTS_90);
            path.lineTo(rect.right, rect.bottom - mAngle);

            path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle,
                    rect.right, rect.bottom), CONSTANTS_0, CONSTANTS_90);
            path.lineTo(rect.left + mAngle, rect.bottom);

            path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle,
                    mAngle + rect.left, rect.bottom), CONSTANTS_90, CONSTANTS_90);
            path.lineTo(rect.left, rect.top + mArrowHeight + mAngle);
            path.arcTo(new RectFloat(rect.left, rect.top + mArrowHeight, mAngle
                    + rect.left, mAngle + rect.top + mArrowHeight), CONSTANTS_180, CONSTANTS_90);
            path.close();
        }

        private void setRightPath(RectFloat rect, Path path) {
            if (isArrowCenter) {
                mArrowPosition = (rect.bottom - rect.top) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
            }

            path.moveTo(rect.left + mAngle, rect.top);
            path.lineTo(rect.getWidth() - mAngle - mArrowWidth, rect.top);
            path.arcTo(new RectFloat(rect.right - mAngle - mArrowWidth,
                    rect.top, rect.right - mArrowWidth, mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);
            path.lineTo(rect.right - mArrowWidth, mArrowPosition);
            path.lineTo(rect.right, mArrowPosition + mArrowHeight / CONSTANTS_2);
            path.lineTo(rect.right - mArrowWidth, mArrowPosition + mArrowHeight);
            path.lineTo(rect.right - mArrowWidth, rect.bottom - mAngle);

            path.arcTo(new RectFloat(rect.right - mAngle - mArrowWidth, rect.bottom - mAngle,
                    rect.right - mArrowWidth, rect.bottom), CONSTANTS_0, CONSTANTS_90);
            path.lineTo(rect.left + mArrowWidth, rect.bottom);

            path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle,
                    mAngle + rect.left, rect.bottom), CONSTANTS_90, CONSTANTS_90);

            path.arcTo(new RectFloat(rect.left, rect.top, mAngle
                    + rect.left, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
            path.close();
        }

        private void setBottomPath(RectFloat rect, Path path) {
            if (isArrowCenter) {
                mArrowPosition = (rect.right - rect.left) / CONSTANTS_2 - mArrowWidth / CONSTANTS_2;
            }
            path.moveTo(rect.left + mAngle, rect.top);
            path.lineTo(rect.getWidth() - mAngle, rect.top);
            path.arcTo(new RectFloat(rect.right - mAngle,
                    rect.top, rect.right, mAngle + rect.top), CONSTANTS_270, CONSTANTS_90);

            path.lineTo(rect.right, rect.bottom - mArrowHeight - mAngle);
            path.arcTo(new RectFloat(rect.right - mAngle, rect.bottom - mAngle - mArrowHeight,
                    rect.right, rect.bottom - mArrowHeight), CONSTANTS_0, CONSTANTS_90);

            path.lineTo(rect.left + mArrowWidth + mArrowPosition, rect.bottom - mArrowHeight);
            path.lineTo(rect.left + mArrowPosition + mArrowWidth / CONSTANTS_2, rect.bottom);
            path.lineTo(rect.left + mArrowPosition, rect.bottom - mArrowHeight);
            path.lineTo(rect.left + Math.min(mAngle, mArrowPosition), rect.bottom - mArrowHeight);

            path.arcTo(new RectFloat(rect.left, rect.bottom - mAngle - mArrowHeight,
                    mAngle + rect.left, rect.bottom - mArrowHeight), CONSTANTS_90, CONSTANTS_90);
            path.lineTo(rect.left, rect.top + mAngle);
            path.arcTo(new RectFloat(rect.left, rect.top, mAngle
                    + rect.left, mAngle + rect.top), CONSTANTS_180, CONSTANTS_90);
            path.close();
        }

        private void setShaderMatrix() {
            Matrix mShaderMatrix = new Matrix();
            int mBitmapWidth = bubblePixelMap.getImageInfo().size.width;
            int mBitmapHeight = bubblePixelMap.getImageInfo().size.height;
            float scaleX = getWidth() / (float) mBitmapWidth;
            float scaleY = getHeight() / (float) mBitmapHeight;
            mShaderMatrix.postScale(scaleX, scaleY);
            mShaderMatrix.postTranslate(mRect.left, mRect.top);
            mPixelMapShader.setShaderMatrix(mShaderMatrix);
        }

        @Override
        public int getWidth() {
            return (int) mRect.getWidth();
        }

        @Override
        public int getHeight() {
            return (int) mRect.getHeight();
        }

        /**
         * Builder
         *
         * @since 2021-06-24
         */
        public static class Builder {
            /**
             * 默认的箭头宽度
             */
            public static final float DEFAULT_ARROW_WITH = 30;
            /**
             * 默认的箭头高度
             */
            public static final float DEFAULT_ARROW_HEIGHT = 15;
            /**
             * 默认的圆角半径
             */
            public static final float DEFAULT_ANGLE = 20;
            /**
             * 默认的箭头位置
             */
            public static final float DEFAULT_ARROW_POSITION = 20;
            /**
             * 默认的气泡颜色
             */
            public static final Color DEFAULT_BUBBLE_COLOR = Color.WHITE;
            private RectFloat mRect;
            private float mArrowWidth = DEFAULT_ARROW_WITH;
            private float mAngle = DEFAULT_ANGLE;
            private float mArrowHeight = DEFAULT_ARROW_HEIGHT;
            private float mArrowPosition = DEFAULT_ARROW_POSITION;
            private Color bubbleColor = DEFAULT_BUBBLE_COLOR;
            private PixelMap bubblePixelMap;
            private BubbleType bubbleType = BubbleType.COLOR;
            private Canvas mCanvas;
            private ArrowLocation mArrowLocation = ArrowLocation.LEFT;
            private boolean isArrowCenter;

            /**
             * 设置矩形
             *
             * @param rect 矩形
             * @return 当前对象
             */
            public Builder rect(RectFloat rect) {
                this.mRect = rect;
                return this;
            }

            /**
             * 设置箭头宽度
             *
             * @param arrowWidth 箭头宽度
             * @return 当前对象
             */
            public Builder arrowWidth(float arrowWidth) {
                this.mArrowWidth = arrowWidth;
                return this;
            }

            /**
             * 设置圆角半径
             *
             * @param angle 圆角半径
             * @return 当前对象
             */
            public Builder angle(float angle) {
                this.mAngle = angle * CONSTANTS_2;
                return this;
            }

            /**
             * 设置箭头高度
             *
             * @param arrowHeight 箭头高度
             * @return 当前对象
             */
            public Builder arrowHeight(float arrowHeight) {
                this.mArrowHeight = arrowHeight;
                return this;
            }

            /**
             * 设置箭头距边位置
             *
             * @param arrowPosition 箭头距边位置
             * @return 当前对象
             */
            public Builder arrowPosition(float arrowPosition) {
                this.mArrowPosition = arrowPosition;
                return this;
            }

            /**
             * 设置气泡颜色
             *
             * @param bubbleColorParam 气泡颜色
             * @return 当前对象
             */
            public Builder bubbleColor(Color bubbleColorParam) {
                this.bubbleColor = bubbleColorParam;
                bubbleType(BubbleType.COLOR);
                return this;
            }

            /**
             * 设置图片
             *
             * @param bubblePixelMapParam 图片
             * @return 当前对象
             */
            public Builder bubblePixelMap(PixelMap bubblePixelMapParam) {
                this.bubblePixelMap = bubblePixelMapParam;
                bubbleType(BubbleType.PIXEL_MAP);
                return this;
            }

            /**
             * 设置箭头位置
             *
             * @param arrowLocationParam 箭头位置
             * @return 当前对象
             */
            public Builder arrowLocation(ArrowLocation arrowLocationParam) {
                this.mArrowLocation = arrowLocationParam;
                return this;
            }

            /**
             * 设置气泡类型
             *
             * @param bubbleTypeParam 气泡类型：背景或图片
             * @return 当前对象
             */
            public Builder bubbleType(BubbleType bubbleTypeParam) {
                this.bubbleType = bubbleTypeParam;
                return this;
            }

            /**
             * 设置箭头是否中心
             *
             * @param isArrowCenterParam 箭头是否中心
             * @return 当前对象
             */
            public Builder arrowCenter(boolean isArrowCenterParam) {
                this.isArrowCenter = isArrowCenterParam;
                return this;
            }

            /**
             * 设置画布
             *
             * @param canvas 画布
             * @return 当前对象
             */
            public Builder setCanvas(Canvas canvas) {
                this.mCanvas = canvas;
                return this;
            }

            /**
             * build
             *
             * @return BubbleElement对象
             */
            public BubbleElement build() {
                BubbleElement bubbleElement = null;
                if (mRect != null) {
                    bubbleElement = new BubbleElement(this);
                }
                return bubbleElement;
            }
        }

        /**
         * 箭头方向
         *
         * @since 2021-06-24
         */
        public enum ArrowLocation {
            /**
             * 方向：左
             */
            LEFT("left", 0x00),
            /**
             * 方向：右
             */
            RIGHT("right", 0x01),
            /**
             * 方向：上
             */
            TOP("top", 0x02),
            /**
             * 方向：下
             */
            BOTTOM("bottom", 0x03);

            private final String mName;
            private final int mValue;

            ArrowLocation(String name, int value) {
                this.mName = name;
                this.mValue = value;
            }

            /**
             * 根据名称获取对象
             *
             * @param name 名称
             * @return 对象
             */
            public static ArrowLocation mapIntToName(String name) {
                for (ArrowLocation value : ArrowLocation.values()) {
                    if (value.getName().equals(name)) {
                        return value;
                    }
                }
                return getDefault();
            }

            public static ArrowLocation getDefault() {
                return LEFT;
            }

            public int getIntValue() {
                return mValue;
            }

            public String getName() {
                return mName;
            }
        }

        /**
         * 气泡类型
         *
         * @since 2021-06-24
         */
        public enum BubbleType {
            /**
             * 设置背景
             */
            COLOR,
            /**
             * 图片
             */
            PIXEL_MAP
        }
    }

}

